#include "texture.h"

#include <GL/gl.h>

#include <cstdlib>

#include <cstring>

Texture::Texture()
{
	w		= 0;
	h		= 0;
	pixels	= NULL;

	id				= 0;
	server_format	= GL_RGBA;
	client_format	= GL_RGBA;

	bound	= false;

	filter	= Linear;
	wrap	= Repeat;
	mipmap	= MeanMipmap;

	for (int fog_index = 0; fog_index < 4; fog_index++) fog_color[fog_index] = 0.f;
	fog_start	= 1;
	fog_enabled	= false;

	loading_type	= NotSet;
	last_error		= Finished;
	cursor			= -1;

	filename	= NULL;
	format		= Auto;

	proc_type	= Noise;
	proc_width	= 0;
	proc_height	= 0;

	loader		= NULL;
}

Texture::~Texture()
{
	StopLoading();

	if (pixels) delete[] pixels;

	if (filename) delete[] filename;

	if (loader) delete loader;
}

// Stop loading whatever the loading state was, which is useful if another load has to begin.
void Texture::StopLoading()
{
	switch (loading_type)
	{
	case File:
		if (filename) delete[] filename;
		filename	= NULL;
		break;

	case Procedural:
		proc_width	= 0;
		proc_height	= 0;
		break;
	}

	if (loader) delete loader;
	loader	= NULL;

	cursor	= -1;
}

// Bind the texture to the GL.
// This method builds mipmaps if needed
void Texture::bind()
{
	if (bound)
	{
		glBindTexture(GL_TEXTURE_2D, id);
		return;
	}
	bound	= true;

	if ((w == 0) || (h == 0)) Log("<Texture::bind()> : Texture size (%d, %d) should not be null.\n", w, h);
	else
	{
		int _w, _h;
		for (_w=w;(_w&1)==0;_w>>=1);
		for (_h=h;(_h&1)==0;_h>>=1);
		if (((_h>>1)!=0) || ((_w>>1)!=0)) Log("<Texture::bind()> : Texture size (%d, %d) should be a power of two.\n", w, h);
	}
	Log("\nBinding texture w==%d h==%d\n", w, h);

	// Generate texture and set the base texture level
	GLint	unpack_alignment;
	glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack_alignment);
	glGenTextures(1, &id);
	glBindTexture(GL_TEXTURE_2D, id);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, server_format, w, h, 0, client_format, GL_UNSIGNED_BYTE, pixels);
	Log("glTexImage2d(0x%x, %d, 0x%x, %d, %d, %d, 0x%x, 0x%x, %p);\n",GL_TEXTURE_2D, 0, server_format, w, h, 0, client_format, GL_UNSIGNED_BYTE, pixels);
	glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);

	// Set texture filtering
	GLenum	mag_filter = (filter==Nearest) ? GL_NEAREST : GL_LINEAR;
	GLenum	min_filter = (filter==Nearest) ? (((mipmap==NoMipmap) && (!fog_enabled)) ? GL_NEAREST : GL_NEAREST_MIPMAP_LINEAR) : (((mipmap==NoMipmap) && (!fog_enabled)) ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);

	// Set texture wrapping
	GLenum	wrap_s_t = (wrap==Clamp) ? GL_CLAMP : GL_REPEAT;	// GL_CLAMP_TO_EDGE ?
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_t);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_s_t);

	// Add mipmaps, including fog is needed
	if ((mipmap!=NoMipmap) || (fog_enabled))
	{
		// Deduce number of components from image type
		int		nb_components;
		switch (client_format)
		{
		case GL_RED :
		case GL_GREEN :
		case GL_BLUE :
		case GL_ALPHA :
		case GL_LUMINANCE :
			nb_components	= 1;
			break;

		case GL_LUMINANCE_ALPHA :
			nb_components	= 2;
			break;

		case GL_RGB :
			nb_components	= 3;
			break;

		case GL_RGBA :
			nb_components	= 4;
			break;

		default:
			w = h = 0;	// Block further mipmaps
			Log("<Texture::bind()> : Unable to set mipmaps for format %d\n", client_format);
		}

		GLint	level;
		for (level = 1; (w > 1) && (h > 1) && ((!fog_enabled) || (level < fog_start)); w/=2, h/=2, level++)
		{
/*			unsigned char*	last_pixel_pointer = pixels + (w/2)*(h/2)*nb_components;
			for (	unsigned char *pixel_pointer = pixels, *quad_pixel_pointer = pixels;
					pixel_pointer < last_pixel_pointer;
					pixel_pointer += nb_components, quad_pixel_pointer += 4*nb_components)
				for (int byte = 0; byte < nb_components; byte++)
					switch (mipmap)
					{
					case NoMipmap :
						pixel_pointer[byte]	= quad_pixel_pointer[byte];
						break;

					case MeanMipmap :
						pixel_pointer[byte]	= (GLubyte)(( (int)quad_pixel_pointer[byte+nb_components*0+nb_components*w*0]
														+ (int)quad_pixel_pointer[byte+nb_components*1+nb_components*w*0]
														+ (int)quad_pixel_pointer[byte+nb_components*0+nb_components*w*1]
														+ (int)quad_pixel_pointer[byte+nb_components*1+nb_components*w*1] ) / 4);
						break;

					case MaxMipmap :
						pixel_pointer[byte]	= quad_pixel_pointer[byte];
						if (quad_pixel_pointer[byte+nb_components*1+nb_components*w*0]> pixel_pointer[byte]) pixel_pointer[byte] = quad_pixel_pointer[byte+nb_components*1+nb_components*w*0];
						if (quad_pixel_pointer[byte+nb_components*0+nb_components*w*1]> pixel_pointer[byte]) pixel_pointer[byte] = quad_pixel_pointer[byte+nb_components*0+nb_components*w*1];
						if (quad_pixel_pointer[byte+nb_components*1+nb_components*w*1]> pixel_pointer[byte]) pixel_pointer[byte] = quad_pixel_pointer[byte+nb_components*1+nb_components*w*1];
					}*/
			unsigned char *pixel_pointer = pixels;
			for (int y = 0; y < h/2; y++)
				for (int x = 0; x < w/2; x++)
				{
					unsigned char *quad_pixel_pointer_0 = pixels+nb_components*( (x*2+0) + w*(y*2+0) );
					unsigned char *quad_pixel_pointer_1 = pixels+nb_components*( (x*2+1) + w*(y*2+0) );
					unsigned char *quad_pixel_pointer_2 = pixels+nb_components*( (x*2+1) + w*(y*2+1) );
					unsigned char *quad_pixel_pointer_3 = pixels+nb_components*( (x*2+0) + w*(y*2+1) );
					for (int byte = 0; byte < nb_components; byte++, pixel_pointer++)
						switch (mipmap)
						{
						case NoMipmap :
							*pixel_pointer	= quad_pixel_pointer_0[byte];
							break;

						case MeanMipmap :
							*pixel_pointer	= (GLubyte)(( (int)quad_pixel_pointer_0[byte]
														+ (int)quad_pixel_pointer_1[byte]
														+ (int)quad_pixel_pointer_2[byte]
														+ (int)quad_pixel_pointer_3[byte] ) / 4);
							break;

						case MaxMipmap :
							*pixel_pointer	= quad_pixel_pointer_0[byte];
							if (quad_pixel_pointer_1[byte] > *pixel_pointer) *pixel_pointer = quad_pixel_pointer_1[byte];
							if (quad_pixel_pointer_2[byte] > *pixel_pointer) *pixel_pointer = quad_pixel_pointer_2[byte];
							if (quad_pixel_pointer_3[byte] > *pixel_pointer) *pixel_pointer = quad_pixel_pointer_3[byte];
						}
				}
			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			glTexImage2D(GL_TEXTURE_2D, level, server_format, w/2, h/2, 0, client_format, GL_UNSIGNED_BYTE, pixels);
			Log("glTexImage2d(0x%x, %d, 0x%x, %d, %d, %d, 0x%x, 0x%x, %p);\n",GL_TEXTURE_2D, level, server_format, w/2, h/2, 0, client_format, GL_UNSIGNED_BYTE, pixels);
			glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
		}
		if ((fog_enabled) && (level >= fog_start) && (w>=1) && (h>=1))
		{
			Log("Starting fog\n");
			// Prepare fog color
			switch (client_format)
			{
			case GL_RED :
//					fog_color[0]	= fog_color[0];
				break;

			case GL_GREEN :
				fog_color[0]	= fog_color[1];
				break;

			case GL_BLUE :
				fog_color[0]	= fog_color[2];
				break;

			case GL_ALPHA :
				fog_color[0]	= fog_color[3];
				break;

			case GL_LUMINANCE :
				fog_color[0]	= fog_color[0]+fog_color[1]+fog_color[2];
				break;

			case GL_LUMINANCE_ALPHA :
				fog_color[0]	= fog_color[0]+fog_color[1]+fog_color[2];
				fog_color[1]	= fog_color[3];
				break;

			case GL_RGB :
//					fog_color[0]	= fog_color[0];
//					fog_color[1]	= fog_color[1];
//					fog_color[2]	= fog_color[2];
				break;

			case GL_RGBA :
//					fog_color[0]	= fog_color[0];
//					fog_color[1]	= fog_color[1];
//					fog_color[2]	= fog_color[2];
//					fog_color[3]	= fog_color[3];
				break;

			default:
				w = h = 0;	// Block further mipmaps
				Log("<Texture::bind()> : Unable to set mipmapped fog for format %d\n", client_format);
			}

			// Convert floating fog color to bytes
			GLubyte	fog_color_byte[4];
			for (int byte = 0; byte < nb_components; byte++)
				if (fog_color[byte] <= 0.f) fog_color_byte[byte] = 0x00;
				else if (fog_color[byte] >= 1.f) fog_color_byte[byte] = 0xFF;
				else fog_color_byte[byte] = (GLubyte)(fog_color[byte] * 255.f);

			// Fill fog texture
			w	/= 2;
			h	/= 2;
			for (unsigned char* fog_pixels = pixels+(w*h-1)*nb_components; fog_pixels >= pixels; fog_pixels -= nb_components)
				for (int i = 0; i < nb_components; i++)
					fog_pixels[i]	= fog_color_byte[i];

			// Load fog mipmaps to the GL
			while ((w >= 1) && (h >= 1))
			{
				glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
				glTexImage2D(GL_TEXTURE_2D, level, server_format, w, h, 0, client_format, GL_UNSIGNED_BYTE, pixels);
				glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_alignment);
				level++;
				w	/= 2;
				h	/= 2;
			}
		}
	}

	StopLoading();
	last_error	= Finished;

	Log("Texture bound\n", w, h);
}

Texture::LoadingState Texture::load(int nb_bytes)
{
	if (bound)
	{
		Log("<Texture::load(%d)> : invalid call since texture has already been bound at least once.\n", nb_bytes);
		return last_error = ErrorAlreadyBound;
	}
	if ((last_error != InProgress) || (nb_bytes == 0)) return last_error;

	if ((cursor == -1) && (loader == NULL))
		if (loading_type == Procedural)
			switch (proc_type)
			{
			case Noise :
				loader	= new NoiseLoader(this, proc_width, proc_height);
				break;

			case NoiseRGB :
				loader	= new NoiseRGBLoader(this, proc_width, proc_height);
				break;

			default :
				Log("<Texture::load(%d)> : unknown procedural type %d\n", nb_bytes, proc_type);
				return last_error = ErrorUnknownProc;
			}
		else if (loading_type == File)
		{
			// Automatic detection for file format
			if (format == Auto)
			{
				int len = strlen(filename);
				if ((len >= 5)
					&& (filename[len-4]=='.')
					&& ((filename[len-3]=='b') || (filename[len-3]=='B'))
					&& ((filename[len-2]=='m') || (filename[len-2]=='M'))
					&& ((filename[len-1]=='p') || (filename[len-1]=='P')) )
				{
					format	= Bitmap;	// The file format is Bitmap is the file extension is ".BMP"
				}
				else if ((len >= 5)
					&& (filename[len-4]=='.')
					&& ((filename[len-3]=='t') || (filename[len-3]=='T'))
					&& ((filename[len-2]=='g') || (filename[len-2]=='G'))
					&& ((filename[len-1]=='a') || (filename[len-1]=='A')) )
				{
					format	= Targa;	// The file format is Targa is the file extension is ".TGA"
				}

			}

			switch (format)
			{
			case Bitmap :
				loader	= new BmpLoader(this, filename);
				break;

			case Targa :
				loader = new TgaLoader(this, filename);
				break;

			case Auto :	// If the format is still Auto here, that means we couldn't choose a file format amongst the ones we support.
				Log("<Texture::load(%d)> : unable to detect automatically the file format for file %s\n", nb_bytes, filename);
				return last_error = ErrorUnknownFormat;

			default :
				Log("<Texture::load(%d)> : unknown file format type %d\n", nb_bytes, format);
				return last_error = ErrorUnknownFormat;
			}
		}

	return last_error = loader->load(nb_bytes);
}

void Texture::setFilename(const char *_filename, Texture::FileFormat _format)
{
	if (bound)
	{
		Log("<Texture::setFilename(%s, %d)> : invalid call since texture has already been bound at least once.\n", _filename, (int)_format);
		return;
	}
	StopLoading();
	loading_type	= File;

	if (_filename == NULL)
	{
		last_error	= ErrorNullFilename;
		return;
	}
	filename	= new char[strlen(_filename)+1];
	strcpy(filename, _filename);

	last_error	= InProgress;
}

void Texture::setProcedure(Texture::ProceduralType type, unsigned int width, unsigned int height)
{
	if (bound)
	{
		Log("<Texture::setProcedure(%d, %d, %d)> : invalid call since texture has already been bound at least once.\n", (int)type, width, height);
		return;
	}
	StopLoading();
	loading_type	= Procedural;

	proc_type	= type;
	proc_width	= width;
	proc_height	= height;

	last_error	= InProgress;
}

void Texture::setFilter(Texture::TextureFilter _filter)
{
	if (bound)
	{
		Log("<Texture::setFilter(%d)> : invalid call since texture has already been bound at least once.\n", (int)_filter);
		return;
	}

	if ((_filter != Nearest) && (_filter != Linear))
	{
		Log("<Texture::setFilter(%d)> : Parameter must be either Texture::Nearest or Texture::Linear, not something like GL_NEAREST or GL_LINEAR !\n", (int)_filter);
		return;
	}

	filter	= _filter;
}

void Texture::setWrap(Texture::TextureWrap _wrap)
{
	if (bound)
	{
		Log("<Texture::setWrap(%d)> : invalid call since texture has already been bound at least once.\n", (int)_wrap);
		return;
	}

	if ((_wrap != Clamp) && (_wrap != Repeat))
	{
		Log("<Texture::setWrap(%d)> : Parameter must be either Texture::Clamp or Texture::Repeat, not GL_CLAMP or GL_REPEAT !\n", (int)_wrap);
		return;
	}

	wrap	= _wrap;
}

void Texture::setMipmaps(Texture::TextureMipmapping mipmapping)
{
	if (bound)
	{
		Log("<Texture::setMipmaps(%d)> : invalid call since texture has already been bound at least once.\n", (int)mipmapping);
		return;
	}

	if ((mipmapping != NoMipmap) && (mipmapping != MeanMipmap) && (mipmapping != MaxMipmap))
	{
		Log("<Texture::setMipmaps(%d)> : Parameter must be either Texture::NoMipmap or Texture::MeanMipmap or Texture::MaxMipmap, not something like GL_NEAREST_MIPMAP_NEAREST or GL_LINEAR_MIPMAP_LINEAR !\n", (int)mipmapping);
		return;
	}

	mipmap	= mipmapping;
}

void Texture::setFogMipmap(float fog_red, float fog_green, float fog_blue, float fog_alpha, unsigned int start_level)
{
	if (bound)
	{
		Log("<Texture::setFogMipmap({ %f, %f, %f, %f }, %d)> : invalid call since texture has already been bound at least once.\n", fog_red, fog_green, fog_blue, fog_alpha, start_level);
		return;
	}

	if (start_level == 0)
	{
		Log("<Texture::setFogMipmap({ %f, %f, %f, %f }, %d)> : fog starting level should be greater or equal than 1.\n", fog_red, fog_green, fog_blue, fog_alpha, start_level);
	}

	fog_color[0]	= fog_red;
	fog_color[1]	= fog_green;
	fog_color[2]	= fog_blue;
	fog_color[3]	= fog_alpha;
	fog_start		= start_level;
	fog_enabled		= true;
}

void Texture::setFogMipmap(const float _fog_color[4], unsigned int start_level)
{
	setFogMipmap(_fog_color[0], _fog_color[1], _fog_color[2], _fog_color[3], start_level);
}

Texture::BmpLoader::BmpLoader(Texture* _texture, const char* _filename) : FileLoader(_texture, _filename)
{
	file		= NULL;
	palette		= NULL;
	data		= NULL;
}

Texture::BmpLoader::~BmpLoader()
{
	if (file) fclose(file);
	file		= NULL;
	if (palette) delete[] palette;
	palette		= NULL;
	if (data) delete[] data;
	data		= NULL;
}

Texture::LoadingState Texture::BmpLoader::load(int nb_bytes)
{
	// Load bitmap using SDL
	if (nb_bytes == -1)
	{
		SDL_Surface* sdl_bitmap = SDL_LoadBMP(filename);
		if (sdl_bitmap == NULL) return ErrorFileNotFound;

		texture->w	= sdl_bitmap->w;
		texture->h	= sdl_bitmap->h;
		texture->client_format	= GL_RGB;
		texture->server_format	= GL_RGB;
		texture->pixels	= new unsigned char[texture->w * texture->h * 3];

		for (int i=texture->w*texture->h-1; i>=0; i--)
		{
            unsigned char *output = &texture->pixels[3*i];
            unsigned char *input = (unsigned char*)(sdl_bitmap->pixels)+3*i;
			output[0] = input[2];
			output[1] = input[1];
			output[2] = input[0];
		}

		SDL_FreeSurface(sdl_bitmap);

		return Finished;
	}

	// Start loading
	if (texture->cursor == -1)
	{
		file	= fopen(filename, "rb");
		if (file == NULL) return ErrorFileNotFound;
		if (fread(&BmpHeader, sizeof(BmpHeader), 1, file)!=1) return ErrorFileCorrupted;
		endian();

		// We only accept Windows Bitmaps, not OS/2
		if ((BmpHeader.identifier[0]!='B') || (BmpHeader.identifier[1]!='M')) return ErrorFileCorrupted;
		if (BmpHeader.header_size!=0x28) return ErrorFileCorrupted;

		// We only accept non-compressed data
		if (BmpHeader.compression!=0) return ErrorFileCorrupted;

		// Check that data size is rounded to the next 4-byte boundary
		if ((BmpHeader.data_size&0x3)!=0x0) return ErrorFileCorrupted;

		// We only accept 24-bit and 32-bit data
		switch (BmpHeader.bpp)
		{
		case 24:
			data	= new unsigned char[BmpHeader.width*BmpHeader.height*3];
			break;

		case 32:
			data	= new unsigned char[BmpHeader.width*BmpHeader.height*4];
			break;

		default:
			return ErrorFileCorrupted;
		}

		// Advance file cursor to the data
		if (BmpHeader.data_offset > 0x36)
		{
			unsigned char* _void = new unsigned char[BmpHeader.data_offset - 0x36];
			if (fread(_void, BmpHeader.data_offset - 0x36, 1, file)!=1) return ErrorFileCorrupted;
		}
		else if (BmpHeader.data_offset < 0x36) return ErrorFileCorrupted;
		texture->cursor	= BmpHeader.data_offset;
	}

	// Continue loading
	int advance = BmpHeader.file_size;
	if ((nb_bytes != -1) && ((texture->cursor+nb_bytes) < advance))
	{
		advance			= texture->cursor+nb_bytes;
	}
	int i;
	for (i = texture->cursor; i < advance; i++) if (fread(&data[i-BmpHeader.data_offset], 1, 1, file)!=1) return ErrorFileCorrupted;

	// Finish loading if we reached the end of file
	if ((texture->cursor=advance) == BmpHeader.file_size)
	{
		texture->w				= BmpHeader.width;
		texture->h				= BmpHeader.height;
		texture->client_format	= GL_RGB;
		texture->server_format	= GL_INTENSITY;
		texture->pixels			= new unsigned char[BmpHeader.width*BmpHeader.height*3];
		switch (BmpHeader.bpp)
		{
		case 24:
			for (i=BmpHeader.width*BmpHeader.height-1; i>=0; i--)
			{
				texture->pixels[3*i+0] = data[3*i+2];
				texture->pixels[3*i+1] = data[3*i+1];
				texture->pixels[3*i+2] = data[3*i+0];
			}
			break;

		case 32:
			for (i=BmpHeader.width*BmpHeader.height-1; i>=0; i--)
			{
				texture->pixels[3*i+0] = data[4*i+2];
				texture->pixels[3*i+1] = data[4*i+1];
				texture->pixels[3*i+2] = data[4*i+0];
			}
			break;

		default:
			return ErrorFileCorrupted;
		}
		return Finished;
	}
	else
	{
		return InProgress;
	}
}

void Texture::BmpLoader::endian()
{
	// Test if the current processor works little-endian or big-endian, and revert data taken from the header if needed
	union
	{
		unsigned short _short;
		unsigned char _char[2];
	};
	_short	= 1;
	if (_char[0]==0)
	{
#define	REVERT_2(ptr) { unsigned char _swap=ptr[0]; ptr[0]=ptr[1]; ptr[1]=_swap; }
#define	REVERT_4(ptr) { unsigned char _swap=ptr[3]; ptr[3]=ptr[0]; ptr[0]=_swap; _swap=ptr[2]; ptr[2]=ptr[1]; ptr[1]=_swap; }
//		REVERT_2(BmpHeader._identifier);
		REVERT_4(BmpHeader._file_size);
//		REVERT_4(BmpHeader._reserved);
		REVERT_4(BmpHeader._data_offset);
		REVERT_4(BmpHeader._header_size);
		REVERT_4(BmpHeader._width);
		REVERT_4(BmpHeader._height);
		REVERT_2(BmpHeader._planes);
		REVERT_2(BmpHeader._bpp);
		REVERT_4(BmpHeader._compression);
		REVERT_4(BmpHeader._data_size);
		REVERT_4(BmpHeader._hres);
		REVERT_4(BmpHeader._vres);
		REVERT_4(BmpHeader._colors);
		REVERT_4(BmpHeader._important);
#undef	REVERT_4
#undef	REVERT_2
	}
}

Texture::TgaLoader::TgaLoader(Texture* _texture, const char* _filename) : FileLoader(_texture, _filename)
{
	file		= NULL;
	data		= NULL;
}

Texture::TgaLoader::~TgaLoader()
{
	if (file) fclose(file);
	file		= NULL;
	if (data) delete[] data;
	data		= NULL;
}

Texture::LoadingState Texture::TgaLoader::load(int nb_bytes)
{
	// Start loading
	if (texture->cursor == -1)
	{
		GLubyte	TGAheader[12] = {0,0,2,0,0,0,0,0,0,0,0,0};
		GLubyte TGAcompare[12];

		file	= fopen(filename, "rb");
		if (file == NULL) return ErrorFileNotFound;
		if (fread(TGAcompare, sizeof(TGAcompare), 1, file)!=1) return ErrorFileCorrupted;
		if (memcmp(TGAheader, TGAcompare, sizeof(TGAheader))!=0) return ErrorFileCorrupted;
		if (fread(header, sizeof(header), 1, file)!=1) return ErrorFileCorrupted;

		width	= header[1] * 256 + header[0];
		height	= header[3] * 256 + header[2];

		// We do not accept null images
		if ((width<=0) || (height<=0)) return ErrorFileCorrupted;

		// We only accept 24-bit and 32-bit data
		switch (header[4])
		{
		case 24:
			bpp	= 3;
			break;

		case 32:
			bpp	= 4;
			break;

		default:
			return ErrorFileCorrupted;
		}

		// Allocate bytes for the texture data
		size	= width*height*bpp;
		data	= new unsigned char[size];
		if (data==NULL) return ErrorOutOfMemory;

		// Start to read the data (note : we ignore the few bytes read by now)
		texture->cursor	= 0;
	}

	// Continue loading
	int advance = size;
	if ((nb_bytes != -1) && ((texture->cursor+nb_bytes) < advance))
	{
		advance	= texture->cursor+nb_bytes;
	}
	int i;
	for (i = texture->cursor; i < advance; i++) if (fread(&data[i], 1, 1, file)!=1) return ErrorFileCorrupted;

	// Finish loading if we reached the end of file
	if ((texture->cursor=advance) == size)
	{
		texture->w				= width;
		texture->h				= height;
		texture->client_format	= GL_RGB;
		texture->server_format	= GL_INTENSITY;
		texture->pixels			= new unsigned char[width*height*3];
		switch (header[4])
		{
		case 24:
			for (i=width*height-1; i>=0; i--)
			{
				texture->pixels[3*i+0] = data[3*i+2];
				texture->pixels[3*i+1] = data[3*i+1];
				texture->pixels[3*i+2] = data[3*i+0];
			}
			break;

		case 32:
			for (i=width*height-1; i>=0; i--)
			{
				texture->pixels[3*i+0] = data[4*i+2];
				texture->pixels[3*i+1] = data[4*i+1];
				texture->pixels[3*i+2] = data[4*i+0];
			}
			break;

		default:
			return ErrorFileCorrupted;
		}
		return Finished;
	}
	else
	{
		return InProgress;
	}
}
	
Texture::NoiseLoader::NoiseLoader(Texture* _texture, unsigned int _width, unsigned int _height) : ProceduralLoader(_texture, _width, _height)
{
}

Texture::NoiseLoader::~NoiseLoader()
{
}

Texture::LoadingState Texture::NoiseLoader::load(int nb_bytes)
{
	LoadingState loading_state;

	// Start loading
	if (texture->cursor == -1)
	{
		texture->w				= width;
		texture->h				= height;
		texture->pixels			= new unsigned char[width*height];
		texture->client_format	= GL_LUMINANCE;
		texture->server_format	= GL_LUMINANCE;
		texture->cursor			= 0;
	}

	// Continue loading
	int advance = width*height;
	if ((nb_bytes != -1) && ((texture->cursor+nb_bytes) < advance))
	{
		advance			= texture->cursor+nb_bytes;
		loading_state	= InProgress;
	}
	else
	{
		loading_state	= Finished;
	}
	for (int i = texture->cursor; i < advance; i++) texture->pixels[i] = rand()%0x100;
	texture->cursor	= advance;

	// Return analysis
	return loading_state;
}

Texture::NoiseRGBLoader::NoiseRGBLoader(Texture* _texture, unsigned int _width, unsigned int _height) : ProceduralLoader(_texture, _width, _height)
{
}

Texture::NoiseRGBLoader::~NoiseRGBLoader()
{
}

Texture::LoadingState Texture::NoiseRGBLoader::load(int nb_bytes)
{
	LoadingState loading_state;

	// Start loading
	if (texture->cursor == -1)
	{
		texture->w				= width;
		texture->h				= height;
		texture->pixels			= new unsigned char[width*height*3];
		texture->client_format	= GL_RGB;
		texture->server_format	= GL_RGB;
		texture->cursor			= 0;
	}

	// Continue loading
	int advance = width*height*3;
	if ((nb_bytes != -1) && ((texture->cursor+nb_bytes) < advance))
	{
		advance			= texture->cursor+nb_bytes;
		loading_state	= InProgress;
	}
	else
	{
		loading_state	= Finished;
	}
	for (int i = texture->cursor; i < advance; i++) texture->pixels[i] = rand()%0x100;
	texture->cursor	= advance;

	// Return analysis
	return loading_state;
}
